using System;
using BoxBinary.AspectCache.Core.Broadcast;
using BoxBinary.AspectCache.Core.Helpers;
using BoxBinary.AspectCache.Core.Helpers;
using BoxBinary.AspectCache.Core.Logging;
using BoxBinary.AspectCache.Core.PolicyDefinition;
//using BoxBinary.Logging;

namespace BoxBinary.AspectCache.Core.ReplicationSink
{
    /// <summary>
    /// Provides general methods for producing <see cref="BroadcastEvent"/> instances which are then serialized to disk.
    /// By the use of file replication middleware taking these files to other machines, and the operation of <see cref="SinkWatcher"/>
    /// monitoring the folder for changes as the files arrive, cache entries can be thus broadcast to several machines in a cluster. This 
    /// effectively provides a way of synchronising cache items across AppDomains.
    /// </summary>
    public class SinkManager
    {
        /// <summary>
        /// Creates a <see cref="BroadcastEvent"/> instance and serializes it to disk at the location
        /// speciifed by <see cref="GetReplicationSinkRootedPath"/>.
        /// </summary>
        /// <param name="broadcastEventReason"></param>
        /// <param name="policyBoundPackageToCache"></param>
        public static void Broadcast(BroadcastEventReason broadcastEventReason,
                                     WebPolicyBoundCachePackage policyBoundPackageToCache)
        {
            #region Validate parameters

            if (policyBoundPackageToCache == null)
                throw new ArgumentNullException("policyBoundPackageToCache");

            #endregion

            // Create a new BroadcastEvent
            var broadcastEvent = new BroadcastEvent(broadcastEventReason, policyBoundPackageToCache);

            // Generate the filename for the serialized version of the instance
            string serializationFilename =
                Path.GenerateSafeFilenameForSerialization(Guid.NewGuid().ToString());
            // = GetSafePathForSerialization(Guid.NewGuid().ToString());

            // Box the replication sink root path
            string storeRootDir = GetReplicationSinkRootedPath();

            try
            {
                // First check to see whether the underlying object being broadcasted is serializable.
                // This also means checking the argument parameters of a generic type such as a generic List.
                if (broadcastEvent.Package.Item != null &&
                    !IsTypeSerializable(broadcastEvent.Package.Item.GetType()))
                {
                    // The item is not serializable fully, so instead raise a "NotBroadcastable" event
                    // which will be interpreted as a 'delete' by SinkWatcher modules running
                    // on the other servers
                    broadcastEventReason = BroadcastEventReason.ItemUpdatedNotBroadcastable;
                    // Update 2007 03 07 (yes, a day before launch...) by Alex N
                    // DON'T set the Package.Item to be null. What a rookie mistake. It's a reference type,
                    // therefore you've just handily set the contents of the Package, which is stored in our
                    // local RAM cache, to null. Great one!
                    // ... Instead, create a new Package for this broadcastEvent which exists purely for serialization
                    // to disk anyway.
                    // Old: broadcastEvent.Package.Item = null;
                    // New:
                    broadcastEvent.Package = ((PolicyBoundCachePackage) broadcastEvent.Package).Clone();
                    // Cast to PolicyBoundCachePackage to obtain the Clone implementation
                    // NOW we can set broadcastEvent.Package.Item to be null
                    broadcastEvent.Package.Item = null;
                    broadcastEvent.Reason = broadcastEventReason;
                }

                // Debug only: Write to the trace log
                SinkWatcher.ConditionalDebug_WriteToTraceLog(
                                                                "Broadcast " + broadcastEventReason,
                                                                policyBoundPackageToCache.Key);

                string fullPathForSerialization;

                // Serialize the instance to disk
                Serialization.SerializeToFile(storeRootDir, serializationFilename, broadcastEvent,
                                              out fullPathForSerialization);
            }
            catch (Exception broadcastException)
            {
                ErrorHelper.LogError(
                                        new Exception(
                                            string.Format(
                                                             "An error ('{0}') occurred saving a WebPolicyBoundCachePackage to disk; It was saved to the memory cache of this machine, but changes have not been propagated to other machines in the cluster.",
                                                             broadcastException.Message),
                                            broadcastException), true);
            }
        }

        /// <summary>
        /// Deserializes a <see cref="BroadcastEvent"/> object from a file.
        /// </summary>
        /// <param name="fileName">The filename of the file. Note that this is combined with the replication sink root path provided in <see cref="GetReplicationSinkRootedPath"/></param>
        /// <returns></returns>
        public static BroadcastEvent Deserialize(string fileName)
        {
            #region Validate Parameters

            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            if (System.IO.Path.IsPathRooted(fileName))
                throw new ArgumentOutOfRangeException("fileName", fileName, "Path must not be rooted");

            #endregion

            return
                SerializationHelper.DeserializeCacheBroadcastEvent(System.IO.Path.Combine(
                                                                                             GetReplicationSinkRootedPath
                                                                                                 (), fileName));
        }

        /// <summary>
        /// Returns the absolute path of the replication sink root.
        /// </summary>
        /// <returns></returns>
        public static string GetReplicationSinkRootedPath()
        {
            return Path.StaticMapPath(Configuration.Current.CacheStateReplicationSinkPath);
        }

        /// <summary>
        /// Generates an invalid-character safe path for saving the serialized file, combined with the
        /// replication sink root.
        /// </summary>
        /// <param name="fileNameSeed"></param>
        /// <returns></returns>
        public static string GenerateSafePathForSerialization(string fileNameSeed)
        {
            #region Validate Parameters

            if (string.IsNullOrEmpty(fileNameSeed))
                throw new ArgumentNullException("fileNameSeed");

            #endregion

            return Path.GenerateSafePathForSerialization(GetReplicationSinkRootedPath(), fileNameSeed);
        }

        /// <summary>
        /// Identifies whether a type is serializable
        /// </summary>
        /// <param name="sourceType"></param>
        /// <returns></returns>
        public static bool IsTypeSerializable(Type sourceType)
        {
            if (sourceType.IsGenericType || sourceType.IsGenericTypeDefinition)
            {
                foreach (Type type in sourceType.GetGenericArguments())
                {
                    if (!type.IsSerializable)
                        return false;
                }
            }
            return sourceType.IsSerializable;
        }
    }
}